using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection.Emit;
using System.Reflection;

// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation; either version 2.1
// of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
// MA 02111-1307, USA.
// 
// Flee# - A port of Eugene Ciloci's Flee to C#
// Copyright © 2012 Yoni Gozman
//

namespace Ciloci.Flee
{

	// Handles implicit conversions
	internal class ImplicitConverter
	{

        // Table of results for binary operations using primitives
		private static Type[,] OurBinaryResultTable;
        // Primitive types we support
		private static Type[] OurBinaryTypes;

		static ImplicitConverter()
		{
			// Create a table with all the primitive types
			Type[] types = {
				typeof(char),
				typeof(byte),
				typeof(sbyte),
				typeof(Int16),
				typeof(UInt16),
				typeof(Int32),
				typeof(UInt32),
				typeof(Int64),
				typeof(UInt64),
				typeof(float),
				typeof(double)
			};
			OurBinaryTypes = types;
			Type[,] table = new Type[types.Length, types.Length];
			OurBinaryResultTable = table;
			FillIdentities(types, table);

			// Fill the table
			AddEntry(typeof(UInt32), typeof(UInt64), typeof(UInt64));
			AddEntry(typeof(Int32), typeof(Int64), typeof(Int64));
			AddEntry(typeof(UInt32), typeof(Int64), typeof(Int64));
			AddEntry(typeof(Int32), typeof(UInt32), typeof(Int64));
			AddEntry(typeof(UInt32), typeof(float), typeof(float));
			AddEntry(typeof(UInt32), typeof(double), typeof(double));
			AddEntry(typeof(Int32), typeof(float), typeof(float));
			AddEntry(typeof(Int32), typeof(double), typeof(double));
			AddEntry(typeof(Int64), typeof(float), typeof(float));
			AddEntry(typeof(Int64), typeof(double), typeof(double));
			AddEntry(typeof(UInt64), typeof(float), typeof(float));
			AddEntry(typeof(UInt64), typeof(double), typeof(double));
			AddEntry(typeof(float), typeof(double), typeof(double));

			// Byte
			AddEntry(typeof(byte), typeof(byte), typeof(Int32));
			AddEntry(typeof(byte), typeof(sbyte), typeof(Int32));
			AddEntry(typeof(byte), typeof(Int16), typeof(Int32));
			AddEntry(typeof(byte), typeof(UInt16), typeof(Int32));
			AddEntry(typeof(byte), typeof(Int32), typeof(Int32));
			AddEntry(typeof(byte), typeof(UInt32), typeof(UInt32));
			AddEntry(typeof(byte), typeof(Int64), typeof(Int64));
			AddEntry(typeof(byte), typeof(UInt64), typeof(UInt64));
			AddEntry(typeof(byte), typeof(float), typeof(float));
			AddEntry(typeof(byte), typeof(double), typeof(double));

			// SByte
			AddEntry(typeof(sbyte), typeof(sbyte), typeof(Int32));
			AddEntry(typeof(sbyte), typeof(Int16), typeof(Int32));
			AddEntry(typeof(sbyte), typeof(UInt16), typeof(Int32));
			AddEntry(typeof(sbyte), typeof(Int32), typeof(Int32));
			AddEntry(typeof(sbyte), typeof(UInt32), typeof(long));
			AddEntry(typeof(sbyte), typeof(Int64), typeof(Int64));
			//invalid -- AddEntry(GetType(SByte), GetType(UInt64), GetType(UInt64))
			AddEntry(typeof(sbyte), typeof(float), typeof(float));
			AddEntry(typeof(sbyte), typeof(double), typeof(double));

			// int16
			AddEntry(typeof(Int16), typeof(Int16), typeof(Int32));
			AddEntry(typeof(Int16), typeof(UInt16), typeof(Int32));
			AddEntry(typeof(Int16), typeof(Int32), typeof(Int32));
			AddEntry(typeof(Int16), typeof(UInt32), typeof(long));
			AddEntry(typeof(Int16), typeof(Int64), typeof(Int64));
			//invalid -- AddEntry(GetType(Int16), GetType(UInt64), GetType(UInt64))
			AddEntry(typeof(Int16), typeof(float), typeof(float));
			AddEntry(typeof(Int16), typeof(double), typeof(double));

			// Uint16
			AddEntry(typeof(UInt16), typeof(UInt16), typeof(Int32));
			AddEntry(typeof(UInt16), typeof(Int16), typeof(Int32));
			AddEntry(typeof(UInt16), typeof(Int32), typeof(Int32));
			AddEntry(typeof(UInt16), typeof(UInt32), typeof(UInt32));
			AddEntry(typeof(UInt16), typeof(Int64), typeof(Int64));
			AddEntry(typeof(UInt16), typeof(UInt64), typeof(UInt64));
			AddEntry(typeof(UInt16), typeof(float), typeof(float));
			AddEntry(typeof(UInt16), typeof(double), typeof(double));

			// Char
			AddEntry(typeof(char), typeof(char), typeof(Int32));
			AddEntry(typeof(char), typeof(UInt16), typeof(UInt16));
			AddEntry(typeof(char), typeof(Int32), typeof(Int32));
			AddEntry(typeof(char), typeof(UInt32), typeof(UInt32));
			AddEntry(typeof(char), typeof(Int64), typeof(Int64));
			AddEntry(typeof(char), typeof(UInt64), typeof(UInt64));
			AddEntry(typeof(char), typeof(float), typeof(float));
			AddEntry(typeof(char), typeof(double), typeof(double));
		}


		private ImplicitConverter()
		{
		}

		private static void FillIdentities(Type[] typeList, Type[,] table)
		{
			for (int i = 0; i <= typeList.Length - 1; i++) {
				Type t = typeList[i];
				table[i, i] = t;
			}
		}

		private static void AddEntry(Type t1, Type t2, Type result)
		{
			int index1 = GetTypeIndex(t1);
			int index2 = GetTypeIndex(t2);
			OurBinaryResultTable[index1, index2] = result;
			OurBinaryResultTable[index2, index1] = result;
		}

		private static int GetTypeIndex(Type t)
		{
			return System.Array.IndexOf(OurBinaryTypes, t);
		}

		public static bool EmitImplicitConvert(Type sourceType, Type destType, FleeILGenerator ilg)
		{
			if (object.ReferenceEquals(sourceType, destType)) {
				return true;
			} else if (EmitOverloadedImplicitConvert(sourceType, destType, ilg) == true) {
				return true;
			} else if (ImplicitConvertToReferenceType(sourceType, destType, ilg) == true) {
				return true;
			} else {
				return ImplicitConvertToValueType(sourceType, destType, ilg);
			}
		}

		private static bool EmitOverloadedImplicitConvert(Type sourceType, Type destType, FleeILGenerator ilg)
		{
			// Look for an implicit operator on the destination type
			MethodInfo mi = Utility.GetSimpleOverloadedOperator("Implicit", sourceType, destType);

			if (mi == null) {
				// No match
				return false;
			}

			if ((ilg != null)) {
				ilg.Emit(OpCodes.Call, mi);
			}

			return true;
		}

		private static bool ImplicitConvertToReferenceType(Type sourceType, Type destType, FleeILGenerator ilg)
		{
			if (destType.IsValueType == true) {
				return false;
			}

			if (object.ReferenceEquals(sourceType, typeof(Null))) {
				// Null is always convertible to a reference type
				return true;
			}

			if (destType.IsAssignableFrom(sourceType) == false) {
				return false;
			}

			if (sourceType.IsValueType == true) {
				if ((ilg != null)) {
					ilg.Emit(OpCodes.Box, sourceType);
				}
			}

			return true;
		}

		private static bool ImplicitConvertToValueType(Type sourceType, Type destType, FleeILGenerator ilg)
		{
			// We only handle value types
			if (sourceType.IsValueType == false & destType.IsValueType == false) {
				return false;
			}

			// No implicit conversion to enum.  Have to do this check here since calling GetTypeCode on an enum will return the typecode
			// of the underlying type which screws us up.
			if (sourceType.IsEnum == true | destType.IsEnum == true) {
				return false;
			}

			return EmitImplicitNumericConvert(sourceType, destType, ilg);
		}

// Emit an implicit conversion (if the ilg is not null) and returns a value that determines whether the implicit conversion
// succeeded
		public static bool EmitImplicitNumericConvert(Type sourceType, Type destType, FleeILGenerator ilg)
		{
			TypeCode sourceTypeCode = Type.GetTypeCode(sourceType);
			TypeCode destTypeCode = Type.GetTypeCode(destType);

			switch (destTypeCode) {
				case TypeCode.Int16:
					return ImplicitConvertToInt16(sourceTypeCode, ilg);
				case TypeCode.UInt16:
					return ImplicitConvertToUInt16(sourceTypeCode, ilg);
				case TypeCode.Int32:
					return ImplicitConvertToInt32(sourceTypeCode, ilg);
				case TypeCode.UInt32:
					return ImplicitConvertToUInt32(sourceTypeCode, ilg);
				case TypeCode.Double:
					return ImplicitConvertToDouble(sourceTypeCode, ilg);
				case TypeCode.Single:
					return ImplicitConvertToSingle(sourceTypeCode, ilg);
				case TypeCode.Int64:
					return ImplicitConvertToInt64(sourceTypeCode, ilg);
				case TypeCode.UInt64:
					return ImplicitConvertToUInt64(sourceTypeCode, ilg);
				default:
					return false;
			}
		}


		private static bool ImplicitConvertToInt16(TypeCode sourceTypeCode, FleeILGenerator ilg)
		{
			switch (sourceTypeCode) {
				case TypeCode.Byte:
				case TypeCode.SByte:
				case TypeCode.Int16:
					return true;
				default:
					return false;
			}
		}

		private static bool ImplicitConvertToUInt16(TypeCode sourceTypeCode, FleeILGenerator ilg)
		{
			switch (sourceTypeCode) {
				case TypeCode.Char:
				case TypeCode.Byte:
				case TypeCode.UInt16:
					return true;
				default:
					return false;
			}
		}

		private static bool ImplicitConvertToInt32(TypeCode sourceTypeCode, FleeILGenerator ilg)
		{
			switch (sourceTypeCode) {
				case TypeCode.Char:
				case TypeCode.Byte:
				case TypeCode.SByte:
				case TypeCode.Int16:
				case TypeCode.UInt16:
				case TypeCode.Int32:
					return true;
				default:
					return false;
			}
		}

		private static bool ImplicitConvertToUInt32(TypeCode sourceTypeCode, FleeILGenerator ilg)
		{
			switch (sourceTypeCode) {
				case TypeCode.Char:
				case TypeCode.Byte:
				case TypeCode.SByte:
				case TypeCode.Int16:
				case TypeCode.UInt16:
				case TypeCode.UInt32:
					return true;
				default:
					return false;
			}
		}

		private static bool ImplicitConvertToDouble(TypeCode sourceTypeCode, FleeILGenerator ilg)
		{
			switch (sourceTypeCode) {
				case TypeCode.Char:
				case TypeCode.SByte:
				case TypeCode.Byte:
				case TypeCode.Int16:
				case TypeCode.UInt16:
				case TypeCode.Int32:
				case TypeCode.Single:
				case TypeCode.Int64:
					EmitConvert(ilg, OpCodes.Conv_R8);
					break;
				case TypeCode.UInt32:
				case TypeCode.UInt64:
					EmitConvert(ilg, OpCodes.Conv_R_Un);
					EmitConvert(ilg, OpCodes.Conv_R8);
					break;
				case TypeCode.Double:
					break;
				default:
					return false;
			}

			return true;
		}

		private static bool ImplicitConvertToSingle(TypeCode sourceTypeCode, FleeILGenerator ilg)
		{
			switch (sourceTypeCode) {
				case TypeCode.Char:
				case TypeCode.Byte:
				case TypeCode.SByte:
				case TypeCode.Int16:
				case TypeCode.UInt16:
				case TypeCode.Int32:
				case TypeCode.Int64:
					EmitConvert(ilg, OpCodes.Conv_R4);
					break;
				case TypeCode.UInt32:
				case TypeCode.UInt64:
					EmitConvert(ilg, OpCodes.Conv_R_Un);
					EmitConvert(ilg, OpCodes.Conv_R4);
					break;
				case TypeCode.Single:
					break;
				default:
					return false;
			}

			return true;
		}

		private static bool ImplicitConvertToInt64(TypeCode sourceTypeCode, FleeILGenerator ilg)
		{
			switch (sourceTypeCode) {
				case TypeCode.SByte:
				case TypeCode.Int16:
				case TypeCode.Int32:
					EmitConvert(ilg, OpCodes.Conv_I8);
					break;
				case TypeCode.Char:
				case TypeCode.Byte:
				case TypeCode.UInt16:
				case TypeCode.UInt32:
					EmitConvert(ilg, OpCodes.Conv_U8);
					break;
				case TypeCode.Int64:
					break;
				default:
					return false;
			}

			return true;
		}

		private static bool ImplicitConvertToUInt64(TypeCode sourceTypeCode, FleeILGenerator ilg)
		{
			switch (sourceTypeCode) {
				case TypeCode.Char:
				case TypeCode.Byte:
				case TypeCode.UInt16:
				case TypeCode.UInt32:
					EmitConvert(ilg, OpCodes.Conv_U8);
					break;
				case TypeCode.UInt64:
					break;
				default:
					return false;
			}

			return true;
		}

		private static void EmitConvert(FleeILGenerator ilg, OpCode convertOpcode)
		{
			if ((ilg != null)) {
				ilg.Emit(convertOpcode);
			}
		}

// Get the result type for a binary operation
		public static Type GetBinaryResultType(Type t1, Type t2)
		{
			int index1 = GetTypeIndex(t1);
			int index2 = GetTypeIndex(t2);

			if (index1 == -1 | index2 == -1) {
				return null;
			} else {
				return OurBinaryResultTable[index1, index2];
			}
		}

		public static int GetImplicitConvertScore(Type sourceType, Type destType)
		{
			if (object.ReferenceEquals(sourceType, destType)) {
				return 0;
			}

			if (object.ReferenceEquals(sourceType, typeof(Null))) {
				return GetInverseDistanceToObject(destType);
			}

			if (Utility.GetSimpleOverloadedOperator("Implicit", sourceType, destType) != null) {
				// Implicit operator conversion, score it at 1 so it's just above the minimum
				return 1;
			}

			if (sourceType.IsValueType == true) {
				if (destType.IsValueType == true) {
					// Value type -> value type
					int sourceScore = GetValueTypeImplicitConvertScore(sourceType);
					int destScore = GetValueTypeImplicitConvertScore(destType);

					return destScore - sourceScore;
				} else {
					// Value type -> reference type
					return GetReferenceTypeImplicitConvertScore(sourceType, destType);
				}
			} else {
				if (destType.IsValueType == true) {
					// Reference type -> value type
					// Reference types can never be implicitly converted to value types
					Debug.Fail("No implicit conversion from reference type to value type");
				} else {
					// Reference type -> reference type
					return GetReferenceTypeImplicitConvertScore(sourceType, destType);
				}
			}

            return 0;
		}

		private static int GetValueTypeImplicitConvertScore(Type t)
		{
			TypeCode tc = Type.GetTypeCode(t);

			switch (tc) {
				case TypeCode.Byte:
					return 1;
				case TypeCode.SByte:
					return 2;
				case TypeCode.Char:
					return 3;
				case TypeCode.Int16:
					return 4;
				case TypeCode.UInt16:
					return 5;
				case TypeCode.Int32:
					return 6;
				case TypeCode.UInt32:
					return 7;
				case TypeCode.Int64:
					return 8;
				case TypeCode.UInt64:
					return 9;
				case TypeCode.Single:
					return 10;
				case TypeCode.Double:
					return 11;
				case TypeCode.Decimal:
					return 11;
				case TypeCode.Boolean:
					return 12;
				case TypeCode.DateTime:
					return 13;
				default:
					Debug.Assert(false, "unknown value type");
					return -1;
			}
		}

		private static int GetReferenceTypeImplicitConvertScore(Type sourceType, Type destType)
		{
			if (destType.IsInterface == true) {
				return 100;
			} else {
				return GetInheritanceDistance(sourceType, destType);
			}
		}

		private static int GetInheritanceDistance(Type sourceType, Type destType)
		{
			int count = 0;
			Type current = sourceType;

			while ((!object.ReferenceEquals(current, destType))) {
				count += 1;
				current = current.BaseType;
			}

			return count * 1000;
		}

		private static int GetInverseDistanceToObject(Type t)
		{
			int score = 1000;
			Type current = t.BaseType;

			while ((current != null)) {
				score -= 100;
				current = current.BaseType;
			}

			return score;
		}
	}
}
